package com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.evaler;

import static com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.evaler.BaseJSEvaluatorUtils.*;
import static com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.global.BaseJSConstants.copyArguments;
import static com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.interper.BaseJSInterperUtils.readFrom;
import static com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.interper.BaseJSInterperUtils.readUntil;
import static com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.interper.BaseJSInterperUtils.removeTrailingSpaces;

import java.io.StringReader;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.VariableAPI.util.strings.splitters.Splitters;
import com.nulldev.util.io.IOUtils;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.BaseJSEnvironment;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.BaseJSFunction;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.BaseJSObject;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.BaseJSVariable;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.MissingFunctionError;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.UnknownOperationError;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.global.BaseJSNativeFunction;
import com.nulldev.util.scripting.ScriptAPI.baseJS.legacy.lexer.interper.BaseJSInterper;

public class BaseJSEvaluator {

	/*
	 * The BaseJS.Evaluator is a lexer that isn't a hacky fucked-up mess compared to
	 * pre-0.0.7.
	 * 
	 * TODO: - Add multi-thread support. (where possible) Completed: - Fix objects
	 * being registered into the global environment.
	 */

	private static final Logger log = LoggerFactory.getLogger();

	public static class BaseJSEvaluatedArgument {

	}

	public static enum VariableType {
		REGULAR,
		CONSTANT,
		LET_CONST
	}

	private static void processValue(final VariableType type, final String name, final BaseJSEnvironment e, final String value) {
		if (isObject(value)) {
			final String innerObject = value.substring(1, value.length() - 1);
			final StringReader sr = new StringReader(innerObject);
			final BaseJSObject o = new BaseJSObject(name);
			while (true) {
				final String iName = readUntil(sr, ':');
				if (iName == null || iName.isEmpty())
					break;
				final String iValue = readValue(sr);
				if (iValue == null)
					throw new NullPointerException("[processValue::isObject] Illegal iValue!");
				if (iValue.isEmpty())
					break;
				if (iValue.startsWith("function(")) {
					final String[] a = Splitters.splitString(StringsUtil.substringBetweenO(iValue, "(", "){"), ",");
					final String[] operations = Splitters.splitString(StringsUtil.substringBetweenO(iValue, "){", "}"), ";");
					final BaseJSFunction func = new BaseJSFunction(iName.trim(), a, operations);
					o.contents.put(iName.trim(), func);
					log.trace("[processValue::isObject] new(function): " + func);
				} else {
					throw new UnknownOperationError("Cannot operate on value: " + value);
					// TODO+FIXME: Add variable in objects support.
				}
			}
			e.contents.put(name, o);
			IOUtils.closeQuietly(sr);
		} else if (isFunction(value)) {
			final String[] a = Splitters.splitString(StringsUtil.substringBetweenO(value, "(", "){"), ",");
			final String[] operations = Splitters.splitString(StringsUtil.substringBetweenO(value, "){", "}"), ";");
			final BaseJSFunction func = new BaseJSFunction(name.trim(), a, operations); // TODO+FIXME: Add object-access support. (AB.CD=1)
			e.contents.put(name.trim(), func);
		} else if (value.contains("(") && value.endsWith(")")) {
			final String func = readUntil(value, '(');
			if (!e.contents.containsKey(func))
				throw new MissingFunctionError("Unknown function: " + func);
			final BaseJSObject f = e.contents.get(func);
			if (!f.isFunction())
				throw new MissingFunctionError("Not a function: " + func);
			// TODO: Add variable support.
			if (f.isNativeFunction())
				((BaseJSNativeFunction) f).run(e.isUsingNewInterpreter(), e);
			else
				((BaseJSFunction) f).run(e.isUsingNewInterpreter(), e);
		} else {
			throw new UnknownOperationError("Unknown value: " + value);
		}
	}

	private static void process(final BaseJSEnvironment e, final String payload) {
		if (isVariableDecl(payload)) {
			final String[] left = Splitters.splitString(readUntil(payload, '='), " ");
			VariableType type;
			final String _l = ArrayUtils.StringArrayUtils.at(left, 0, "");
			if (_l.equals("const")) {
				type = VariableType.CONSTANT;
			} else if (_l.equals("let")) {
				type = VariableType.LET_CONST;
			} else if (_l.equals("var")) {
				type = VariableType.REGULAR;
			} else {
				log.warn("Unknown variable type: " + _l);
				type = VariableType.REGULAR;
			}
			final String varName = ArrayUtils.StringArrayUtils.at(left, 1, null);
			if (varName == null)
				throw new NullPointerException("[process::isVariableDecl] varName became a nullptr!");
			final String value = readFrom(payload, '=');
			processValue(type, varName, e, value);
		} else if (isDecl(payload)) {
			final String l = readUntil(payload, '=');
			final String value = readFrom(payload, '=');
			processValue(VariableType.REGULAR, l, e, value);
		} else if (isFunctionReference(payload)) {
			final String obj = removeTrailingSpaces(readUntil(payload, '.'));
			final String func = readUntil(payload, '(');
			final String _values = readUntil(readFrom(payload, '('), ')');
			final String[] values = Splitters.splitString(_values.isEmpty() ? "" : _values.substring(0, _values.length() - 1), ",");
			final Object[] vars = new Object[values.length];
			final BaseJSFunction function = e.getFunction(func);
			copyArguments(values, vars, function);
			BaseJSObject _o = function.getObject(func, e);
			if (_o instanceof BaseJSVariable)
				_o = (BaseJSObject) ((BaseJSVariable) _o).get();
			if (_o == null)
				throw new NullPointerException("Function " + obj + " not found!");
			else if (!_o.contains(func) && (!_o.isFunction() && !_o.isNativeFunction()))
				throw new MissingFunctionError(func + " wasn't found in: " + _o);
			final BaseJSObject o = _o.isFunction() || _o.isNativeFunction() ? _o : _o.get(func);
			if (!o.isFunction())
				throw new MissingFunctionError(obj + " isn't a function!");
			if (o.isNativeFunction()) {
				((BaseJSNativeFunction) o).run(e.isUsingNewInterpreter(), e, vars);
			} else {
				((BaseJSFunction) o).run(e.isUsingNewInterpreter(), e, vars);
			}
		} else {
			log.warn("Unknown process: " + payload);
		}
	}

	public static BaseJSEvaluatedArgument eval(final BaseJSEnvironment e, String payload) {
		if (payload == null || payload.isEmpty())
			return null;
		final StringReader sr = new StringReader(payload);
		/*
		 * if (payload.contains("function(") FIXME: This breaks parsing the v8 cipher.
		 * && payload.contains("){") && payload.contains("};")) { process(e, payload);
		 * }else
		 */ if (payload.contains("=") && !payload.contains(";") && !payload.contains("\n")) {
			// assume signular assignment.
			final String left = readUntil(sr, '=');
			final String right = readUntilEOF(sr);
			JVM.println(left + ": " + right);
		} else if (payload.contains("=") && (payload.contains(";") || payload.contains("\n"))) {
			String iter;
			while (!(iter = readUntilEither(sr, ALLOWED_SEPERATORS)).isEmpty()) {
				process(e, iter);
			}
		} else if (payload.contains("+") || payload.contains("Math.pow") || payload.contains("-")) {
			final Object o = BaseJSInterper.evalStatement(payload, e, null);
			if (payload.startsWith("return "))
				e.setReturnValue(o);
		} else {
			log.warn("[eval] unknown behaviour for: " + payload);
		}
		IOUtils.closeQuietly(sr);
		return null;
	}

	public static void main(String[] args) {
		Arguments.setup(args);
		final BaseJSEnvironment f = new BaseJSEnvironment(true);
		eval(f, "var Ew={yJ:function(a,b){a.splice(0,b)}," + "QX:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c},"
				+ "AX:function(a){a.reverse()}};_NG=function(a){a=a.split(\"\");Ew.AX(a,52);Ew.yJ(a,3);Ew.QX(a,1);Ew.QX(a,67);Ew.yJ(a,2);Ew.QX(a,8);return a.join(\"\")};");
		JVM.println(f.contents);
		final Object r = ((BaseJSFunction) f.get("_NG")).run(f,
				"=ced0VE01xBOOo8QYNrKI4ArnJrchMv60gR6zyRZywXpAEiA-rrkQmeyxVWhLkEIPwD3r21gHBAIeihkHSw5BLGWPnMAhIgAw8JQ0qORUORUO");
		JVM.println("v8-ntube-result: " + r);
		final boolean p = ((String) r)
				.equalsIgnoreCase("AOq0QJ8wRgIhAMnPWGLB5wSHkhieIABHg12r3DwPIEkLhWVxyemQkrr-AiEApXwyZUyz6Rg06vMhcrJnrA4IKrNYQ8oOOBx10EV0dec=");
		JVM.println("result: " + (p ? "PASS" : "FAIL"));
	}
}
